/**
 * 处理过程/挂起
 * @config $.TEMP string 缓存及临时文件绝对路径
 * 		日志存文件时, 必需
 * @config $._proc function|null 存文件日志替换
 * 		日志存文件时, 可选
 * @param opt json 选项
 * 		hold: boolean 是否挂起模式  默认:false
 * 			- 差异,影响: log(), end()
 * 			true:  挂起模式  无结束(服务)
 * 			false: 过程模式  处理完后结束
 * 		log: boolean [NODE]日志是否存文件 默认:true
 * 			存储: {$.TEMP}/log/{date('/Y-m/d')}.log
 * 		head: string|null [过程模式]日志头信息
 * 			- 过程模式 ? 必需 : 忽略,  内置替换:
 * 			{KEY}=>this.got(KEY)  KEY含花括号{}
 * 			date:开始时间, use:开始到结束前的用时
 * 			mem:处理开始内存(仅NODE环境有效)
 * @return this
 */
$.proc = function(opt){
	const mem=GEN.RUN?process.memoryUsage().external:'';
	const unix=new Date()*1, time=Math.floor(unix/1000);
	const date=$.date(time);  let {hold,log,head}=opt;
	const logHead = hold ? null : // 挂起模式:日志头无效
		head.replace('{mem}',mem).replace('{date}',date);
	const TMP = {logHead,logTemp:[],logPrev:unix,end:[]};
	TMP._end=null;  hold=!!hold;  log=!!(log??true);
	Object.defineProperty(this, '__proc', {value:TMP});
	const V=Object.freeze({mem,log,unix,time,date,hold});
	Object.defineProperty(this, '__proc__', {value:V});
};
$._proc=$._proc||null; //[配置]存日志文本替换(txt,this)
$.proc.prototype = {
	constructor: $.proc,
	/**
	 * 取得实例数据或内置
	 * @param k string|null 取得键  null:全部
	 * 		hold: 是否挂起模式  $.proc()参数opt.hold
	 * 		log: 是否日志存文件  $.proc()参数opt.log
	 * 		mem: int 实例化时已使用的内存量
	 * 		unix: int 实例化时的时间戳(毫秒)
	 * 		time: int 实例化时的时间戳(秒)
	 * 		date: string 实例化时的时间  Y-m-d H:i:s
	 * @return json|mixed
	 */
	got(k=null){ const m=this.__proc__; return k?m[k]:m; },
	/**
	 * 取得实例化时的时间戳
	 * @param u boolean 是否取毫秒
	 * @return int this.got(u?'unix':'time')
	 */
	now(u=false){ return this.__proc__[u?'unix':'time']; },
	/**
	 * 取得语言消息
	 * @config this._lang $.lang()参数able 默认:null
	 * @param key string 语言编码
	 * @param args json|null 语言模板参数
	 * @return string
	 */
	msg(key, args=null){ return $.lang(key,args,this._lang); },
	/**
	 * 日志:打印+文件(NODE)
	 * @param mix mixed|null 日志内容 type=控制类型:忽略
	 * @param type string 类型  日志/控制 可用:
	 * 		'': mix=Error/$.err() ? 自动识别 : 否则DEBUG
	 * 		日志类型:  $.log的键  ERROR, FAIL, DONE, ...
	 * 		控制类型:  仅opt.hold=false(过程模式)可用
	 * 			head: 取得头信息  save.head  替换'{use}'
	 * 			full: 取得已记录  head(替换'{use}')+main
	 * @return this
	 */
	log(mix, type=''){
		// 控制类型
		if(type==='head'){
			const use = (T._end||new Date()*1) - M.unix;
			return T.logHead.replace('{use}', $.dateUse(use));
		}
		if(type==='full'){
			const t=T.logTemp, h=this.log(null,'head');
			let m=t?t.join('\n'):''; return m?h+'\n'+m:h;
		}
		// 日志内容
		if(this.__proc._log) $.err('$.log.save.already'); // 已存
		if(mix instanceof Error) mix=$.err('$.log.err',mix,true);
		if(mix instanceof $.err) return mix.log(type, this);
		const T=this.__proc, M=this.__proc__;
		// 日志类型
		let pre=$.__log(type||'DEBUG'), m=M.mem;
		if(m) m = ' '+$.byte(process.memoryUsage().external-m);
		if(M.hold){
			let ext=`\x1B[90m${$.date()}${m}\x1B[0m`, str;
			pre=`# ${pre} [${ext}]`; str=typeof mix==='string';
			if(!str) console.log('\n'+pre,mix); else{
				let log=$._log?$._log(mix,this):mix;
				console.log('\n'+pre+' '+log);
			}

		}
		else{
			let p=T.logPrev; T.logPrev=new Date()*1; if(m) m+='+';
			pre += `[\x1B[90m${m+$.dateUse(T.logPrev-p)}\x1B[0m]`;
		}
		const txt = pre+' '+$.txt(mix).trim();
		M.hold ? this.__log(txt) : T.logTemp.push(txt);
		return this;
	},
	/**
	 * 注册:结束后执行
	 * @param fn function|null 回调函数
	 * 		null: 取结束时间 -> _end()
	 * @return this|int|null
	 * 		int: [fn=null] 已结束 时间戳
	 * 		null: [fn=null] 未结束
	 */
	end(fn=null){
		if(!fn) return this.__proc._end; this.endErr();
		this.__proc.end.push(fn);  return this;
	},
	/**
	 * 结束判断: 若已结束抛出异常
	 */
	endErr(){ if(this.__proc._end) $.err('$.proc.endErr'); },
	// 结束后调用
	_end(){
		const k='$.proc._end.', M=this.__proc__, hold=M.hold;
		if(hold) return this.log($.err(k+'hold', null, true));
		this.endErr();  const U=this.__proc._end=new Date()*1;
		if($.DEBUG) this.log(k+'before '+$.dateUse(U-M.unix));
		for(const b of this.__proc.end){ // 执行注册的结束回调
			let v, err;  try{ v=b(this); }catch(e){ err=e; }
			if(err) this.log($.err(k+'call',err,true));
			else if(v instanceof Promise)
				v.catch(e=>this.log($.err(k+'call',e,true)));
		}
		const DEBUG = $.DEBUG ? k+'after ' : null;
		if(DEBUG) this.log(DEBUG+$.dateUse(new Date()*1-U));
		if(!M.log) return;  const txt=this.log(null,'full');
		console.log('\n'+($._log ? $._log(txt,this) : txt));
		this.__proc._log=true;  return this.__log(txt);
	},
	// [内部]存日志到文件
	__log(txt){
		if(!this.__proc__.log || !GEN.RUN) return;
		const T=$.date(), [Y,M,D]=T.slice(0,10).split('-');
		txt = txt.replace(/\x1B\[(1;)?\d+m/g, '');
		if($._proc) txt=$._proc(txt,this);  txt+='\n\n';
		const DIR=$.TEMP+`/log/${Y}-`+M, TO=DIR+`/${D}.log`;
		const P=this.constructor.prototype;  let L=P.__log_;
		L = L || (P.__log_=$.track((dir,to,txt)=>{
			const B=()=>$.fsWrite(to,txt,{flag:'a+'});
			return $.fsMkdir(dir).then(B);
		}));
		L(DIR,TO,txt).catch(e=>$.err('$.proc.__log',e));
	},
};
